package ua.yandex.prodcons.utilconcurrent;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CircularBuffer {

    private final int size;
    private final Condition notFull;
    private final Condition notEmpty;
    private final ReentrantLock lock;
    private int freePlace;
    private int start;
    private int lastNoFilled;
    private final Object[] array;

    CircularBuffer(int n) {
        size = n;
        freePlace = n;
        start = 0;
        array = new Object[n];
        lastNoFilled = 0;
        lock = new ReentrantLock();
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }

    public void add(Object data) throws InterruptedException {
        lock.lock();
        try {
            if (freePlace == 0) {
                notFull.await();
            }
            array[lastNoFilled] = data;
            lastNoFilled++;
            lastNoFilled %= size;
            freePlace--;
            notEmpty.signal();
        } catch (InterruptedException ex) {
            Logger.getLogger(CircularBuffer.class.
                    getName()).log(Level.SEVERE, null, ex);
        } finally {
            lock.unlock();
        }
    }

    public Object get() throws ExecutionException, InterruptedException {
        lock.lock();
        try {
            if (freePlace == size) {
                notEmpty.await();
            }
            Object result = array[start];
            start++;
            start %= size;
            freePlace++;
            notFull.signal();
            return result;
        } finally {
            lock.unlock();
        }
    }
}
